using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Framework.Core.Cache
{
    public class DictionaryStorage : IStorage
    {
        private readonly TimeSpan expiration;
        private readonly int size;
        private readonly IDictionary<string, CacheEntry> storage = new Dictionary<string, CacheEntry>();
        private readonly ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim();
        private int collectCount;
        private int hit;
        private int missed;

        public DictionaryStorage(int size, TimeSpan expiration)
        {
            this.size = size;
            this.expiration = expiration;
        }

        public bool TryGet(string key, out object cache)
        {
            try
            {
                readerWriterLockSlim.EnterReadLock();
                if (IsCached(key))
                {
                    hit++;
                    cache = storage[key].Value;
                    return true;
                }

                missed++;
                cache = null;
                return false;
            }
            finally
            {
                readerWriterLockSlim.ExitReadLock();
            }
        }

        public void Update(string key, TimeSpan callDuration, object value)
        {
            try
            {
                readerWriterLockSlim.EnterWriteLock();
                if (IsOutOfSize())
                {
                    Collect();
                    if (IsOutOfSize())
                    {
                        RemoveFirstMinRank();
                    }
                }
                storage[key] = new CacheEntry(callDuration, value);
            }
            finally
            {
                readerWriterLockSlim.ExitWriteLock();
            }
        }

        public void Flush()
        {
            try
            {
                readerWriterLockSlim.EnterWriteLock();
                storage.Clear();
            }
            finally
            {
                readerWriterLockSlim.ExitWriteLock();
            }
        }

        private bool IsCached(string key)
        {
            return storage.ContainsKey(key) && storage[key].IsValid(expiration);
        }

        private void RemoveFirstMinRank()
        {
            var min = storage.OrderBy(x => x.Value.Rank).First();
            storage.Remove(min.Key);
        }

        private bool IsOutOfSize()
        {
            return storage.Count >= size;
        }

        private void Collect()
        {
            collectCount++;

            var candidates = storage.Where(s => !s.Value.IsValid(expiration)).Select(s => s.Key).ToList();

            candidates.ForEach(c => storage.Remove(c));

            foreach (var cacheEntry in storage)
            {
                cacheEntry.Value.ResetStatistics();
            }
        }
    }
}